package boss.portal.security;

import boss.portal.filter.JWTAuthenticationFilter;
import boss.portal.filter.JWTAuthorizationFilter;
import boss.portal.handler.*;
import boss.portal.service.impl.CustomAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author chenwen
 * SpringSecurity的配置
 * 通过SpringSecurity的配置，将JWTLoginFilter，JWTAuthenticationFilter组合在一起
 * 如果配置了下面的SessionCreationPolicy.STATELESS，则SpringSecurity不会保存session会话，在/logout登出的时候会拿不到用户实体对象。
 * http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 * @EnableGlobalMethodSecurity @Secured权限注解
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 需要放行的URL白名单
     */
    private static final String[] AUTH_WHITELIST = {
            // -- register url
            "/users/signup",
            "/users/addTask",
            // -- swagger ui
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**"
            // other public endpoints of your API may be appended to this array
    };

    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler;

    // 设置 HTTP 验证规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer = http.cors()
                //暂时禁用CSRF，否则无法提交表单
                .and().csrf().disable()
                //配置取消session管理
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //统一结果处理 403 异常
                .exceptionHandling().authenticationEntryPoint(new AuthEntryPoint())
                .and()
                .authorizeRequests()
                //白名单放行
                .antMatchers(AUTH_WHITELIST).permitAll()
                //剩余请求验证
                .anyRequest()
                // 验证后可以访问
                .authenticated()
                .and()
                //配置登录,检测到用户未登录时跳转的url地址,登录放行
                .formLogin()
                //需要跟前端表单的action地址一致
                .loginProcessingUrl("/login")
                //账号密码成功之后调用
                .successHandler(customAuthenticationSuccessHandler)
                //登录失败时调用
                .failureHandler(simpleUrlAuthenticationFailureHandler)
                .and()
                //匿名登录时候调用
                .exceptionHandling().authenticationEntryPoint(new Http401AuthenticationEntryPoint("Basic realm=\"MyApp\""))
                .and()
                //无权限时候调用
                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
                .and()
                //认证
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                //授权
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // 默认注销行为为logout，可以通过下面的方式来修改
                .logout()
                .logoutUrl("/logout")
                // 设置注销成功后跳转页面，默认是跳转到登录页面;
                .logoutSuccessUrl("/login")
                //注销的时候调用
                .logoutSuccessHandler(customLogoutSuccessHandler)
                .permitAll();
    }

    /**
     * 身份验证
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 自定义密码校验的规则
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder));
    }

    /**
     * 配置拦截资源
     */
    @Override
    public void configure(WebSecurity web) {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/js/**", "/css/**", "/img/**");
    }
}
